package com.fingard.crypto;

import cn.topca.api.cert.CertApiException;
import cn.topca.api.cert.CertSet;
import cn.topca.api.cert.CertStore;
import com.cfca.util.pki.api.*;
import com.cfca.util.pki.cert.X509Cert;
import com.cfca.util.pki.cipher.JCrypto;
import com.cfca.util.pki.cipher.JKey;
import com.cfca.util.pki.cipher.Session;
import com.fingard.diagnostics.LogHelper;
import com.fingard.dsp.bank.directConfig.ActSetItem;
import com.fingard.text.StringHelper;
import com.fingard.FGBiz;
import org.apache.commons.codec.binary.Base64;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.*;
import java.security.cert.Certificate;
import java.util.Enumeration;

public class SignVerifyHelper {
	private Session session = null;

	public SignVerifyHelper() {
		try {
			JCrypto jcrypto = JCrypto.getInstance();
			jcrypto.initialize("JSOFT_LIB", null);
			this.session = jcrypto.openSession("JSOFT_LIB");
		} catch (Exception ex) {
			ex.printStackTrace();
			System.out.println(ex.toString());
		}
	}

	/**
	 * 获取公钥证书
	 *
	 * @param p_ksType
	 * @param p_ksPath
	 * @param p_kPass
	 * @param p_sbLog
	 * @return
	 */
	public static byte[] getBytePublicKey(String p_ksType, String p_ksPath, String p_kPass, StringBuilder p_sbLog){
        FileInputStream fis = null;
        try{
			KeyStore ks = KeyStore.getInstance(p_ksType);//"PKCS12"
			fis = new FileInputStream(p_ksPath);
			char[] keyPassword = null;
			keyPassword = p_kPass.toCharArray();
			ks.load(fis, keyPassword);
            fis.close();
            fis = null;
			@SuppressWarnings("rawtypes")
			Enumeration enumAlias = ks.aliases();
			String keyAlias = null;
			if (enumAlias.hasMoreElements()){
				keyAlias = (String)enumAlias.nextElement();
			}
			Certificate cer = ks.getCertificate(keyAlias);
			return cer.getEncoded();
		}catch(Exception ex){
			p_sbLog.append(ex.getMessage());
		} finally {
            try {
                if(fis != null){
                    fis.close();
                }
            }catch(Exception ex){
                p_sbLog.append(ex.getMessage());
            }
        }
		return null;
	}

	/**
	 * 签名
	 * @param p_toSign 待签名数据
	 * @param p_ksType KeyStore类型，如"PKCS12"
	 * @param p_algorithm 签名算法，如"SHA1withRSA"
	 * */
	public static byte[] signData(byte[] p_toSign, String p_ksType, String p_algorithm, String p_ksPath, String p_kPass, StringBuilder p_sbLog){
		FileInputStream fis = null;
		try {
			KeyStore ks = KeyStore.getInstance(p_ksType);// "PKCS12"
			fis = new FileInputStream(p_ksPath);
			char[] keyPassword = null;
			if (StringHelper.hasAnyChar(p_kPass)) {
				keyPassword = p_kPass.toCharArray();
			}
			ks.load(fis, keyPassword);
			fis.close();
			fis = null;


			Enumeration<String> enumAlias = ks.aliases();
			String keyAlias = null;
			if (enumAlias.hasMoreElements()) {
				keyAlias = (String) enumAlias.nextElement();
			}
			PrivateKey priKey = (PrivateKey) ks.getKey(keyAlias, keyPassword);

			Signature signProvider = Signature.getInstance(p_algorithm);// "SHA1withRSA"
			signProvider.initSign(priKey);
			signProvider.update(p_toSign);
			return signProvider.sign();
		}
		catch(Exception ex){
			FGBiz.limitMsg.exception("signData", ex,"ksType:"+p_ksType,"ksPath:"+p_ksPath);
			p_sbLog.append(LogHelper.getStackTrace(ex));
		}
		finally{
			try {
				if(fis != null){
					fis.close();
				}
			}
			catch(Exception ex){
				FGBiz.limitMsg.exception("signData", ex);
			}
		}
		return null;
	}

	/**
	 * 验证数字签名函数入口
	 *
	 * @param plainBytes
	 *            待验签明文字节数组
	 * @param signBytes
	 *            待验签签名后字节数组
	 * @param publicKey
	 *            验签使用公钥
	 * @param signAlgorithm
	 *            签名算法
	 * @return 验签是否通过
	 * @throws Exception
	 */
	public static boolean verifySign(byte[] plainBytes, byte[] signBytes, PublicKey publicKey, String signAlgorithm) throws Exception {
		boolean isValid = false;
		try {
			Signature signature = Signature.getInstance(signAlgorithm);
			signature.initVerify(publicKey);
			signature.update(plainBytes);
			isValid = signature.verify(signBytes);
			return isValid;
		} catch (NoSuchAlgorithmException e) {
			throw new Exception(String.format("验证数字签名时没有[%s]此类算法", signAlgorithm));
		} catch (InvalidKeyException e) {
			throw new Exception("验证数字签名时公钥无效");
		} catch (SignatureException e) {
			throw new Exception("验证数字签名时出现异常");
		}
	}

	/**
	 * 签名方法，用了SHA-1压缩 RSA加密
	 */
	public static String signBySHARSA(String p_toSignStr, ActSetItem actItem, StringBuilder p_sbLog, String encoding) {
		try {
			byte[] bytes = sha1X16(p_toSignStr, encoding);
			//System.out.println("SHA1->16进制转换后的摘要=[" + new String(bytes) + "]");
			byte[] signResult = SignVerifyHelper.signData(bytes, "PKCS12", "SHA1withRSA", actItem.ownKeyStorePath,
					actItem.ownKeyPassword, p_sbLog);
			if (signResult != null && p_sbLog.length() == 0) {
				return new String(Base64.encodeBase64(signResult));
			} else {
				return null;
			}
		} catch (Exception ex) {
			p_sbLog.append(ex.getMessage());
		}
		return null;
	}

	/**
	 * RSA公钥加密
	 * @param p_toSignStr
	 * @param actItem
	 * @param p_sbLog
	 * @param encoding
	 * @return
	 */
	public static byte[] encryptByRSA(String p_toSignStr, ActSetItem actItem, StringBuilder p_sbLog, String encoding) {
		try {
			byte[] bytes = sha1(p_toSignStr, encoding);
			//System.out.println("SHA1->16进制转换后的摘要=[" + new String(bytes) + "]");
			byte[] signResult = SignVerifyHelper.signData(bytes, "PKCS12", "SHA1withRSA", actItem.ownKeyStorePath,
					actItem.ownKeyPassword, p_sbLog);
			if (signResult != null && p_sbLog.length() == 0) {
				return signResult;
			} else {
				return null;
			}
		} catch (Exception ex) {
			p_sbLog.append(ex.getMessage());
		}
		return null;
	}

	/**
	 * 把数据先通过SHA-1压缩，在转换成16进制
	 */
	public static byte[] sha1X16(String data, String encoding) {
		byte[] bytes = sha1(data, encoding);
		StringBuilder sha1StrBuff = new StringBuilder();
		for (int i = 0; i < bytes.length; i++)
			if (Integer.toHexString(0xFF & bytes[i]).length() == 1)
				sha1StrBuff.append("0").append(Integer.toHexString(0xFF & bytes[i]));
			else
				sha1StrBuff.append(Integer.toHexString(0xFF & bytes[i]));
		try {
			return sha1StrBuff.toString().getBytes(encoding);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 把数据先通过SHA-1压缩
	 */
	public static byte[] sha1(String datas, String encoding) {
		MessageDigest md = null;
		try {
			byte[] data = datas.getBytes(encoding);
			md = MessageDigest.getInstance("SHA-1");
			md.reset();
			md.update(data);
			return md.digest();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 网商银行业务报文签名
	 * @param configPath 配置文件路径
	 * @param plainData 原文
	 * @param encapsulate 签名结果是否包含原文
	 * @return
	 */
	public static String sign(String configPath,String charsetName,String plainData, boolean encapsulate)throws Exception {
		cn.topca.api.cert.Certificate certificate = null;
		CertSet certSet = null;
		try {
			ConfigTool.getInstance().init(configPath);
			certSet = CertStore.listAllCerts();
			certificate = certSet.get(0);
		} catch (Exception e) {
			e.printStackTrace();
		}
		//是否包含原文
		byte[] signedData = null;
		try {
			//签名
			signedData = certificate.signP7(plainData.getBytes(charsetName), encapsulate);
		} catch (CertApiException e) {
			e.printStackTrace();
			throw new Exception("签名失败");
		}
		String sign = Base64.encodeBase64String(signedData);
		return sign;
	}

	/**
	 * 网商银行文件签名
	 * @param configPath 配置文件路径
	 * @param plainData 原文
	 * @param encapsulate 是否包含原文
	 * @param fo 文件
	 * @throws Exception
	 */
	public static void sign(String configPath,byte[] plainData, boolean encapsulate, FileOutputStream fo)throws Exception {
		cn.topca.api.cert.Certificate certificate = null;
		CertSet certSet = null;
		try {
			ConfigTool.getInstance().init(configPath);
			certSet = CertStore.listAllCerts();
			certificate = certSet.get(0);
		} catch (Exception e) {
			e.printStackTrace();
		}
		//是否包含原文
		byte[] signedData = null;
		try {
			//签名
			signedData = certificate.signP7(plainData, encapsulate);
			fo.write(Base64.encodeBase64(signedData));
			fo.close();
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("签名失败");
		}
		//System.out.println(Base64.encodeBase64String(signedData));
	}

	/**
	 * 通过CFCA加密数据, 金联万家里用.
	 * @param cerPath  密钥地址
	 */
	public String signDateByCFCA(String datas, String cerPath) throws Exception {
		if (datas == null || datas.trim().length() == 0) {
			return "";
		}
		EnvelopUtil envUtil = new EnvelopUtil();
		FileInputStream tmpFileStream = new FileInputStream(cerPath);
		X509Cert cert = new X509Cert(tmpFileStream);
		envUtil.addRecipient(cert);

		envUtil.setCMSFlag();
		byte[] b64MsgEnvelop = envUtil.envelopeMessage(datas.getBytes(), EncryptUtil.RC4, session);

		tmpFileStream.close();
		return new String(b64MsgEnvelop);
	}

	/**
	 * 易宝支付：MD5+证书签名
	 */
	public String buildCertSign(String hmacStr, String pfxName, String pfxPass) throws Exception {
		// 下面用数字证书进行签名
		String signMessage = "";
		String ALGORITHM = SignatureUtil.SHA1_RSA;
		try {
			JKey jkey = KeyUtil.getPriKey(pfxName, pfxPass);
			X509Cert cert = CertUtil.getCert(pfxName, pfxPass);
			X509Cert[] cs = new X509Cert[1];
			cs[0] = cert;
			SignatureUtil signUtil = null;

			// 第二步:对请求的串进行MD5对数据进行签名
			String yphs = DigestHelper.cryptoMD5RetHexStr(hmacStr.getBytes());//TODO
			signUtil = new SignatureUtil();
			byte[] b64SignData;
			// 第三步:对MD5签名之后数据调用CFCA提供的api方法用商户自己的数字证书进行签名
			b64SignData = signUtil.p7SignMessage(true, yphs.getBytes(), ALGORITHM, jkey, cs, session);
			signMessage = new String(b64SignData, "UTF-8");
		} catch (Exception e) {
			throw new Exception("签名失败");
		}
		return signMessage;
	}

	public String buildCertSign(boolean encapsulated,String hmacStr, String pfxName, String pfxPass,String charsetName) throws Exception {
		// 下面用数字证书进行签名
		String signMessage = "";
		String ALGORITHM = SignatureUtil.SHA1_RSA;
		try {
			JKey jkey = KeyUtil.getPriKey(pfxName, pfxPass);
			X509Cert cert = CertUtil.getCert(pfxName, pfxPass);
			X509Cert[] cs = new X509Cert[1];
			cs[0] = cert;
			SignatureUtil signUtil = null;

			signUtil = new SignatureUtil();
			byte[] b64SignData;
			// 第三步:对MD5签名之后数据调用CFCA提供的api方法用商户自己的数字证书进行签名
			b64SignData = signUtil.p7SignMessage(encapsulated, hmacStr.getBytes(charsetName), ALGORITHM, jkey, cs, session);
			signMessage = new String(b64SignData, "UTF-8");
		} catch (Exception e) {
			throw new Exception("签名失败");
		}
		return signMessage;
	}

	public byte[] buildCertSign(boolean encapsulated,byte[] sourceData, String pfxName, String pfxPass) throws Exception {
		// 下面用数字证书进行签名
		byte[] b64SignData;
		String ALGORITHM = SignatureUtil.SHA1_RSA;
		try {
			JKey jkey = KeyUtil.getPriKey(pfxName, pfxPass);
			X509Cert cert = CertUtil.getCert(pfxName, pfxPass);
			X509Cert[] cs = new X509Cert[1];
			cs[0] = cert;
			SignatureUtil signUtil = null;
			signUtil = new SignatureUtil();
			// 第三步:对MD5签名之后数据调用CFCA提供的api方法用商户自己的数字证书进行签名
			b64SignData = signUtil.p7SignMessage(encapsulated, sourceData, ALGORITHM, jkey, cs, session);
		} catch (Exception e) {
			throw new Exception("签名失败");
		}
		return b64SignData;
	}

	/**
	 * 对传入字符串进行CFCA解密，返回解密后字符串数据
	 * @param pfxPath 私钥地址
	 * @param cerPath 公钥地址
	 * @return 解密后字符串
	 */
	public String unEnvelopByCFCA(String msgInfo, String pfxPath, String cerPath, String passWord) throws Exception {
		if (msgInfo == null || msgInfo.trim().length() == 0) {
			return "";
		}
		JKey priKey = KeyUtil.getPriKey(pfxPath, passWord);
		X509Cert pfxCert = CertUtil.getCert(pfxPath, passWord);
		EnvelopUtil envUtil = new EnvelopUtil();
		X509Cert cert = new X509Cert(new FileInputStream(cerPath));
		envUtil.addRecipient(cert);
		envUtil.setCMSFlag();
		byte[] srcMsgInfo = envUtil.openEnvelopedMessage(msgInfo.getBytes(), priKey,
				pfxCert,this.session);
		return new String(srcMsgInfo);
	}

}
